﻿using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace WatchDog
{
    //Start("\"e:/test.exe\"", true);
    // https://blog.csdn.net/slwsss/article/details/88591381
    public class Windows服务启动GUI进程
    {
        public static void Start(string commandLine, bool showWindow)
        {
            IntPtr hToken;
            IntPtr hTokenDup;
            const int TOKEN_ALL_ACCESS = 268435456;
            const int TokenSessionId = 12;
            const uint CREATE_PROCESS_FLAGS = 0x00000020 | 0x00000010 | 0x400;

            if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, out hToken))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            var sa = new SECURITY_ATTRIBUTES();
            sa.bInheritHandle = true;
            sa.Length = Marshal.SizeOf(sa);

            if (!DuplicateTokenEx(hToken, 268435456, ref sa, 1, 1, out hTokenDup))
            {
                var error = Marshal.GetLastWin32Error();
                CloseHandle(hToken);
                throw new Win32Exception(error);
            }

            var si = new STARTUPINFO();
            si.cb = Marshal.SizeOf(si);
            si.lpDesktop = "WinSta0\\Default";
            if (!showWindow)
            {
                si.dwFlags = 1;//STARTF
                si.wShowWindow = 0;
            }
            IntPtr pEnv;
            var dwSessionId = WTSGetActiveConsoleSessionId();

            if (!SetTokenInformation(hTokenDup, TokenSessionId, out dwSessionId, sizeof(uint)))
            {
                var error = Marshal.GetLastWin32Error();
                CloseHandle(hToken);
                CloseHandle(hTokenDup);
                throw new Win32Exception(error);
            }

            if (!CreateEnvironmentBlock(out pEnv, hTokenDup, 0))
            {
                var error = Marshal.GetLastWin32Error();
                CloseHandle(hToken);
                CloseHandle(hTokenDup);
                throw new Win32Exception(error);
            }

            PROCESS_INFORMATION pro;
            if (!CreateProcessAsUser(hTokenDup, null, commandLine, ref sa, ref sa, true, CREATE_PROCESS_FLAGS, pEnv, null, ref si, out pro))
            {
                var error = Marshal.GetLastWin32Error();
                CloseHandle(hToken);
                CloseHandle(hTokenDup);
                throw new Win32Exception(error);
            }

            if (pEnv != IntPtr.Zero)
            {
                DestroyEnvironmentBlock(pEnv);
            }

            CloseHandle(hToken);
            CloseHandle(hTokenDup);
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool SetTokenInformation(IntPtr TokenHandle, int TokenInformationClass, out IntPtr TokenInformation, int TokenInformationLength);
        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr GetCurrentProcess();
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr WTSGetActiveConsoleSessionId();
        [DllImport("Userenv.dll", SetLastError = true)]
        public static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
        [DllImport("Userenv.dll", SetLastError = true)]
        public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, int bInherit);
        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool DuplicateTokenEx(IntPtr hExistingToken, int dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, int ImpersonationLevel, int dwTokenType, out IntPtr phNewToken);
        [DllImport("advapi32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, SetLastError = true)]
        public static extern bool CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, uint dwCreationFlags, IntPtr lpEnvrionment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
        [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool CloseHandle(IntPtr handle);
        public struct SECURITY_ATTRIBUTES
        {
            public int Length;

            public IntPtr lpSecurityDescriptor;

            public bool bInheritHandle;
        }
        public struct STARTUPINFO
        {
            public int cb;

            public string lpReserved;

            public string lpDesktop;

            public string lpTitle;

            public uint dwX;

            public uint dwY;

            public uint dwXSize;

            public uint dwYSize;

            public uint dwXCountChars;

            public uint dwYCountChars;

            public uint dwFillAttribute;

            public uint dwFlags;

            public short wShowWindow;

            public short cbReserved2;

            public IntPtr lpReserved2;

            public IntPtr hStdInput;

            public IntPtr hStdOutput;

            public IntPtr hStdError;
        }
        public struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;

            public IntPtr hThread;

            public int dwProcessID;

            public int dwThreadID;
        }
    }
}
